package com.rollway.restful;

import com.rollway.restful.options.CompositeRestfulOptions;
import com.rollway.restful.options.RestfulOptions;
import com.rollway.restful.support.MethodMetadataParser;
import com.rollway.restful.utils.Util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionLikeType;
import lombok.Setter;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;


@ConfigurationProperties("restful.client.default")
@Setter
public class RestfulConfig {

    private String scheme = "http://";

    private Integer connectTimeout = 5000;

    private Integer readTimeout = 5000;

    private CompositeRestfulOptions compositeRestfulOptions;

    @Bean
    public RestfulOptions defaultOptions() {
        return RestfulOptions.builder()
                .serviceName(Util.DEFAULT)
                .methodKey(Util.DEFAULT)
                .scheme(scheme)
                .options(new RestfulOptions.Options(connectTimeout, readTimeout))
                .build();
    }

    @Bean
    @ConditionalOnMissingBean
    public OkHttpClient okHttpClient() {
        return new OkHttpClient.Builder()
                //设置连接超时
                .connectTimeout(connectTimeout, TimeUnit.SECONDS)
                //设置读超时
                .readTimeout(readTimeout, TimeUnit.SECONDS)
                //设置写超时
                .writeTimeout(60, TimeUnit.SECONDS)
                //是否自动重连
                .retryOnConnectionFailure(false)
                .connectionPool(new ConnectionPool(200, 5L, TimeUnit.MINUTES))
                .build();
    }

    @Bean
    public CompositeRestfulOptions restFulOptionsComposite(List<RestfulOptions> restFulOptions) {
        CompositeRestfulOptions composite = new CompositeRestfulOptions();
        Map<String, List<RestfulOptions>> serviceOptions = restFulOptions.stream()
                .collect(Collectors.groupingBy(RestfulOptions::getServiceName));
        serviceOptions.entrySet().forEach(entry -> {
            CompositeRestfulOptions.CompositeRestfulMethodOptions methodOptionsComposite = entry.getValue().stream()
                    .collect(Collectors.toMap(RestfulOptions::getMethodKey,
                            Function.identity(),
                            ((first, second) -> {
                                throw new RuntimeException(String.format("Invalid RestfulOptions with same serviceName: %s and same methodKey: %s"
                                        , first.getMethodKey(), first.getMethodKey()));
                            }),
                            CompositeRestfulOptions.CompositeRestfulMethodOptions::new));
            composite.put(entry.getKey(), methodOptionsComposite);
        });
        this.compositeRestfulOptions = composite;
        return composite;
    }

    @Bean
    public MethodMetadataParser methodMetadataParser(CompositeRestfulOptions optionsComposite) {
        return new MethodMetadataParser(optionsComposite);
    }

    /**
     *  do not rely too much on configuration ,code's first
     * @param str
     * @throws JsonProcessingException
     */
    @Value("${restful.configs:[]}")
    public void modifyOptions(String str) throws JsonProcessingException {
        if(this.compositeRestfulOptions != null){
            ObjectMapper objectMapper = new ObjectMapper();
            CollectionLikeType type = objectMapper.getTypeFactory().constructCollectionLikeType(List.class, RestfulOptions.class);
            List<RestfulOptions> list = objectMapper.readValue(str, type);
            list.forEach(options ->
                    compositeRestfulOptions.get(options.getServiceName()).put(options.getMethodKey(), options));
        }
    }

    /**
     *  you can call this method to modify configs
     * @return
     */
    public CompositeRestfulOptions getCompositeRestfulOptions() {
        return compositeRestfulOptions;
    }
}
